<?php
/**
 * 亚马逊广告数据同步类
 * 12.19.2
 */
set_time_limit(3600);

class CoreAmazonAdvDataSync
{
    //curl相关
    private $CURLOPT_CONNECTTIMEOUT = 10;   //设置连接等待的最长秒数，建议为10
    private $CURLOPT_TIMEOUT = 40;          //设置CURL允许执行的最长秒数，根据报表文件大小来设置，建议为40，不设置或为0容易卡死
    private $curlError;

    private $sellerStringId;                //当前店铺的卖家编号
    private $marketplaceStringId;           //当前站点编号
    private $profileId;                     //当前店铺的profile_id
    private $marketplaceLocalId;            //当前站点标志
    private $accountEmail;                  //当前帐号邮箱
    private $shopId;                        //当前店铺id

    //↓↓↓广告API配置↓↓↓
    //接口地址前缀
    private $apiEndpoint;

    //接口密钥
    private $clientId = '';
    private $clientSecret = '';

    //回调地址（必须与LwA设置的地址完全相同或在地址列表里）
    private $returnUri = 'https://third.gets.com/amazon/index.php?c=amazonAdvertising&a=advAuthCallback';  //线上
    //↑↑↑广告API配置↑↑↑

    /**
     * 拉取广告活动快照
     * 每次执行只操作一个店铺
     * @param int $profileLocalId  商家profile表的主键，用于指定要操作的店铺
     * @return void
     */
    function actionPullCampaigns($profileLocalId=0)
    {
        //查询一条广告数据同步任务
        $map = array(
            't.state' => 0,    //同步状态：未同步
            'p.profile_id' => array('exp', 't.profile_id')
        );
        if ($profileLocalId) $map['p.id'] = $profileLocalId;
        $sql = 'SELECT t.seller_string_id, t.profile_id, p.country_code 
            FROM amazon_adv_ds_task t, amazon_adv_soa_profile p 
            WHERE '.$this->where($map).' 
            ORDER BY t.last_ds_time, t.id LIMIT 1';
        $task = $this->DbHelper->selectRow($sql);
        if (!$task) exit;

        $this->sellerStringId = $task['seller_string_id'];
        $this->profileId = $task['profile_id'];
        $this->apiEndpoint = $this->getApiEndpoint($task['country_code']);
        $snapshotConf = $this->getSnapshotTaskFieldsConfig();   //快照任务表字段配置

        //查询广告活动快照信息
        $map = array(
            'profile_id' => $this->profileId,
            'record_type' => 1, //广告活动快照
        );
        $sql = 'SELECT * FROM amazon_adv_snapshot_task WHERE '.$this->where($map).' LIMIT 1';
        $row = $this->DbHelper->selectRow($sql);

        //判断快照是否存在
        if ($row){
            //判断同步状态
            if ($row['sync_state'] != 0) exit("sync_state={$row['sync_state']}");

            //判断快照生成状态
            switch ($row['status']){
                case 1: //IN_PROGRESS
                    //查询快照信息
                    $snapshot = $this->apiQuerySnapshot($row['snapshot_id']);

                    //判断新获取的快照生成状态
                    if ($snapshot['status'] == 'IN_PROGRESS') exit;

                    if ($snapshot['status'] == 'SUCCESS'){
                        //更新快照生成信息
                        $sqlData = array(
                            'status' => array_search($snapshot['status'], $snapshotConf['status']),
                            'status_details' => $snapshot['statusDetails'],
                            'location' => $snapshot['location'],
                            'file_size' => $snapshot['fileSize']
                        );
                        $map = array(
                            'id' => $row['id']
                        );
                        $sql = 'UPDATE amazon_adv_snapshot_task SET '.$this->createSaveData($sqlData).' WHERE '.$this->where($map).' LIMIT 1';
                        $sqlRes = $this->DB_Sql->query($sql);
                        if ($sqlRes == false){
                            $this->addApiLog('', '', '', '更新快照同步任务表失败：'.$sql);
                            exit('更新快照同步任务表失败：'.$sql);
                        }
                        exit;
                    }

                    if ($snapshot['status'] == 'FAILURE'){
                        //更新快照生成信息
                        $sqlData = array(
                            'status' => array_search($snapshot['status'], $snapshotConf['status']),
                            'status_details' => $snapshot['statusDetails'],
                            'sync_state' => 3,  //同步失败
                            'sync_error' => $snapshot['statusDetails']
                        );
                        $map = array(
                            'id' => $row['id']
                        );
                        $sql = 'UPDATE amazon_adv_snapshot_task SET '.$this->createSaveData($sqlData).' WHERE '.$this->where($map).' LIMIT 1';
                        $sqlRes = $this->DB_Sql->query($sql);
                        if ($sqlRes == false){
                            $this->addApiLog('', '', '', '更新快照同步任务表失败：'.$sql);
                            exit('更新快照同步任务表失败：'.$sql);
                        }

                        //更新广告数据同步状态为失败
                        $this->updateDsTaskState(3, $snapshot['statusDetails']);
                        exit;
                    }
                    break;

                case 2: //SUCCESS
                    //判断快照文件是否已下载
                    $filename = '/data/aliexpress/advertisingSnapshot/'.$this->profileId.'_campaigns.json.gz';
                    if (!file_exists($filename)){
                        //下载并保存快照
                        $this->downloadSnapshot($row['location'], $filename);
                        exit;
                    }

                    //读取快照内容
                    $data = $this->readGz($filename);
                    $data = json_decode($data, true, 512, JSON_BIGINT_AS_STRING);

                    $campaignConf = $this->getCampaignFieldsConfig();   //广告活动表字段配置

                    //更新或添加到数据库
                    foreach ($data as $k => $v) {
                        //驼峰式键名转下划线命名
                        unset($v['bidding']);
                        $v = $this->arrKeyUncamelize($v);

                        //判断本地库是否存在该记录
                        $map = array(
                            'profile_id' => $this->profileId,
                            'campaign_id' => $v['campaign_id']
                        );
                        $sql = 'SELECT id, sync_state FROM amazon_adv_campaign WHERE '.$this->where($map).' LIMIT 1';
                        $oldRow = $this->DbHelper->selectRow($sql);
                        if ($oldRow){
                            //判断旧记录的同步状态
                            if ($oldRow['sync_state'] != 2) continue;

                            //更新记录（注意要做值转化的字段）
                            $v['campaign_type'] = 1;
                            $v['state'] = array_search($v['state'], $campaignConf['state_en']);
                            $v['start_date'] = strtotime($v['start_date']);
                            if (isset($v['end_date'])) $v['end_date'] = strtotime($v['end_date']);
                            $v['premium_bid_adjustment'] = $v['premium_bid_adjustment']? 1 : 0;
                            $v['last_updated_date'] = time();
                            $v['sync_state'] = 2;   //已同步
                            $v['sync_error_count'] = 0;
                            $v['sync_error'] = '';
                            $map = array(
                                'id' => $oldRow['id']
                            );
                            $sql = 'UPDATE amazon_adv_campaign SET '.$this->createSaveData($v).' WHERE '.$this->where($map).' LIMIT 1';
                            $sqlRes = $this->DB_Sql->query($sql);
                            if ($sqlRes == false){
                                $this->addApiLog('', '', '', '自动更新广告活动失败：'.$sql);

                                //更新快照同步状态为失败
                                $this->updateSnapshotSyncState($row['id'], 3, '自动更新广告活动失败：'.$sql);

                                //更新广告数据同步状态为失败
                                $this->updateDsTaskState(3, '自动更新广告活动失败：'.$sql);

                                exit('自动更新广告活动失败：'.$sql);
                            }
                        }else{
                            //新增记录
                            $v['profile_id'] = $this->profileId;
                            $v['campaign_type'] = 1;
                            $v['state'] = array_search($v['state'], $campaignConf['state_en']);
                            $v['start_date'] = strtotime($v['start_date']);
                            if (isset($v['end_date'])) $v['end_date'] = strtotime($v['end_date']);
                            $v['premium_bid_adjustment'] = $v['premium_bid_adjustment']? 1 : 0;
                            $v['creation_date'] = $v['last_updated_date'] = time();
                            $v['sync_state'] = 2;   //已同步
                            $sqlRes = $this->addRow('amazon_adv_campaign', $v);
                            if ($sqlRes === false){
                                $this->addApiLog('', '', '', '自动新增广告活动失败');

                                //更新快照同步状态为失败
                                $this->updateSnapshotSyncState($row['id'], 3, '自动新增广告活动失败');

                                //更新广告数据同步状态为失败
                                $this->updateDsTaskState(3, '自动新增广告活动失败');

                                exit('自动新增广告活动失败');
                            }
                        }
                    }

                    //同步成功
                    //更新快照同步状态为已同步
                    $this->updateSnapshotSyncState($row['id'], 2);
                    echo '同步成功<br />';

                    //删除快照文件
                    $result = @unlink($filename);
                    if (!$result){
                        $this->addApiLog('', '', '', '删除快照文件失败：'.$filename);
                        echo('删除快照文件失败：'.$filename);    //不需要结束程序
                    }
                    break;
            }

        }else{
            //请求生成广告活动快照
            $snapshot = $this->apiRequestSnapshot('campaigns');

            //添加快照同步任务
            $sqlData = array(
                'profile_id' => $this->profileId,
                'snapshot_id' => $snapshot['snapshotId'],
                'record_type' => 1, //记录类型：广告活动
                'status' => 1,      //快照状态：IN_PROGRESS
                'status_details' => ''
            );
            $sqlRes = $this->DbHelper->add('amazon_adv_snapshot_task', $sqlData);
            if (!$sqlRes){
                $this->addApiLog('', '', '', '添加快照同步任务失败：'.$sql);
                exit('添加快照同步任务失败：'.$sql);
            }
        }
    }

    /**
     * 推送广告活动
     * 规则：每次操作多个店铺；先添加或编辑的数据先推送；按同步操作类型推送；尽可能批量同步，减少API调用次数
     * @param int $p 页码
     * @return void
     */
    function actionPushCampaigns($p){
        //查询店铺信息
        $sql = 'SELECT p.id, p.seller_string_id, p.marketplace_string_id, p.profile_id, p.country_code 
            FROM amazon_adv_soa_profile p 
            ORDER BY p.id LIMIT '.$this->page(10, $p);
        $profiles = $this->DbHelper->selectRows($sql);

        //逐个店铺处理
        foreach ($profiles as $key => $value) {
            echo '操作店铺：'.$value['id'].' Start<br />';

            $this->sellerStringId = $value['seller_string_id'];
            $this->profileId = $value['profile_id'];
            $this->apiEndpoint = $this->getApiEndpoint($value['country_code']);

            //查询该店铺未同步添加的广告活动
            $map = array(
                'profile_id' => $this->profileId,
                'campaign_type' => 1,   //活动类型：产品广告活动
                'sync_state' => 0,      //同步状态：未同步
                'sync_type' => 1,       //要同步的操作：添加
            );
            $sql = 'SELECT * FROM amazon_adv_campaign WHERE ' . $this->where($map) . ' ORDER BY last_updated_date, creation_date LIMIT 10';
            $list = $this->DbHelper->selectRows($sql);

            if ($list) {
                //获取广告活动表字段配置
                $campaignConf = $this->getCampaignFieldsConfig();

                //获取当前店铺的Adv授权码（！由于授权码会过期，每次调用接口前须重新获取）
                $accessToken = $this->getAdvAccessToken($this->sellerStringId);
                if ($accessToken === false) continue;

                //调用接口添加广告活动（！提交重复的数据会返回相同的活动id）
                $url = $this->apiEndpoint . 'campaigns';

                //组装数据包
                $apiData = array();
                foreach ($list as $k => $v) {
                    $row = array(
                        'name' => array('string', $v['name']),
                        'campaignType' => 'sponsoredProducts',
                        'targetingType' => $v['targeting_type'],
                        'state' => $campaignConf['state_en'][$v['state']],   //！不一定是enabled
                        'dailyBudget' => $v['daily_budget'],
                        'startDate' => array('string', date('Ymd', $v['start_date'])),
                        'premiumBidAdjustment' => $v['premium_bid_adjustment'] ? true : false
                    );
                    if ($v['end_date']) $row['endDate'] = array('string', date('Ymd', $v['end_date']));
                    $apiData[] = $row;
                }
                $apiData = $this->arr2jsonCustom($apiData, true);

                $header = array(
                    'Content-Type: application/json',
                    'Authorization: Bearer ' . $accessToken,
                    'Amazon-Advertising-API-Scope: ' . $this->profileId
                );
                $result = $this->curl($url, $apiData, 'post', $header);

                //防止过于频繁调用接口
                sleep(1);

                //接口错误处理
                if ($result === false) { //一般是接口无响应
                    $this->addApiLog($url, $this->curlError, $apiData, 'Call API createCampaigns fail');
                    echo $this->curlError . '<br />';
                    continue;
                }
                if (!is_array(json_decode($result, true))) {    //接口返回错误信息字符
                    $this->addApiLog($url, $result, $apiData, 'Call API createCampaigns fail');
                    echo $result.'<br />';
                    continue;
                }
                //接口返回一维数组或二维数组
                $result = json_decode($result, true, 512, JSON_BIGINT_AS_STRING);
                if (isset($result['code'])) {    //接口返回错误信息数组
                    $this->addApiLog($url, $result, $apiData, 'Call API createCampaigns fail');
                    var_dump($result);
                    continue;
                }

                //组装待更新数据
                $saveData = array();
                foreach ($result as $k => $v) {
                    if ($v['code'] == 'SUCCESS') {   //推送成功
                        $arr = array(
                            'id' => $list[$k]['id'],
                            'campaign_id' => $v['campaignId'],
                            'sync_state' => 2,  //同步状态：已同步
                            //'sync_error_count' => 0,
                            'sync_error' => ''
                        );
                    } else {                          //推送失败
                        $arr = array(
                            'id' => $list[$k]['id'],
                            'campaign_id' => '',
                            'sync_state' => 3,  //同步状态：同步失败
                            //'sync_error_count' => array('exp', 'sync_error_count+1'),
                            'sync_error' => $v['description']
                        );
                        $this->addApiLog($url, $result, $apiData, '推送广告活动失败，记录id：' . $list[$k]['id']);
                    }
                    $saveData[] = $arr;
                }

                //批量更新
                if ($saveData) {
                    $sqlRes = $this->DbHelper->batchUpdate('amazon_adv_campaign', $saveData, 'id');
                    if ($sqlRes === false) {
                        $this->addApiLog($url, $result, $apiData, '更新广告活动表失败：' . $sql);
                        echo '更新广告活动表失败：' . $sql . '<br />';
                        continue;
                    }
                }
            }

            //查询该店铺未同步编辑的广告活动
            $map = array(
                'profile_id' => $this->profileId,
                'campaign_type' => 1,   //活动类型：产品广告活动
                'sync_state' => 0,      //同步状态：未同步
                'sync_type' => 2,       //要同步的操作：编辑
            );
            $sql = 'SELECT * FROM amazon_adv_campaign WHERE ' . $this->where($map) . ' ORDER BY last_updated_date, creation_date LIMIT 10';
            $list = $this->DbHelper->selectRows($sql);

            if ($list) {
                //获取广告活动表字段配置
                $campaignConf = $this->getCampaignFieldsConfig();

                //获取当前店铺的Adv授权码（！由于授权码会过期，每次调用接口前须重新获取）
                $accessToken = $this->getAdvAccessToken($this->sellerStringId);
                if ($accessToken === false) continue;

                //调用接口编辑广告活动
                $url = $this->apiEndpoint . 'campaigns';

                //组装数据包
                $apiData = array();
                foreach ($list as $k => $v) {
                    $row = array(
                        'campaignId' => array('number', $v['campaign_id']),
                        'name' => array('string', $v['name']),
                        'state' => array('string', $campaignConf['state_en'][$v['state']]),
                        'dailyBudget' => array('number', $v['daily_budget']),
                        'premiumBidAdjustment' => $v['premium_bid_adjustment'] ? true : false
                    );
                    if ($v['end_date']) $row['endDate'] = array('string', date('Ymd', $v['end_date']));
                    $apiData[] = $row;
                }
                $apiData = $this->arr2jsonCustom($apiData, true);

                $header = array(
                    'Content-Type: application/json',
                    'Authorization: Bearer ' . $accessToken,
                    'Amazon-Advertising-API-Scope: ' . $this->profileId
                );
                $result = $this->curl($url, $apiData, 'put', $header);

                //防止过于频繁调用接口
                sleep(1);

                //接口错误处理
                if ($result === false) { //一般是接口无响应
                    $this->addApiLog($url, $this->curlError, $apiData, 'Call API updateCampaigns fail');
                    echo $this->curlError.'<br />';
                    continue;
                }
                if (!is_array(json_decode($result, true))) {    //接口返回错误信息字符
                    $this->addApiLog($url, $result, $apiData, 'Call API updateCampaigns fail');
                    echo $result.'<br />';
                    continue;
                }
                //接口返回一维数组或二维数组
                $result = json_decode($result, true, 512, JSON_BIGINT_AS_STRING);
                if (isset($result['code'])) {    //接口返回错误信息数组
                    $this->addApiLog($url, $result, $apiData, 'Call API updateCampaigns fail');
                    var_dump($result);
                    continue;
                }

                //组装待更新数据
                $saveData = array();
                foreach ($result as $k => $v) {
                    if ($v['code'] == 'SUCCESS') {   //推送成功
                        $arr = array(
                            'id' => $list[$k]['id'],
                            'sync_state' => 2,  //同步状态：已同步
                            'sync_error' => ''
                        );
                    } else {                          //推送失败
                        $arr = array(
                            'id' => $list[$k]['id'],
                            'sync_state' => 3,  //同步状态：同步失败
                            'sync_error' => $v['description']
                        );
                        $this->addApiLog($url, $result, $apiData, '推送广告活动失败，记录id：' . $list[$k]['id']);
                    }
                    $saveData[] = $arr;
                }

                //批量更新
                if ($saveData) {
                    $sqlRes = $this->DbHelper->batchUpdate('amazon_adv_campaign', $saveData, 'id');
                    if ($sqlRes === false) {
                        $this->addApiLog($url, $result, $apiData, '更新广告活动表失败：' . $sql);
                        echo '更新广告活动表失败：' . $sql . '<br />';
                    }
                }
            }

            echo '操作店铺：'.$value['id'].' End<br />';
        }

        exit('任务执行完毕');
    }
}